package towerDefense.circlesprites;

import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import towerDefense.towers.CircleSprite;
import com.golden.gamedev.object.CollisionManager;
import com.golden.gamedev.object.Sprite;
import com.golden.gamedev.object.SpriteGroup;
import com.golden.gamedev.object.collision.CollisionRect;
import com.golden.gamedev.object.collision.CollisionShape;


public abstract class CircleSpriteToSpriteCollisionGroup
    extends CollisionManager
{

    // sprite bounding box
    /**
     * Default collision shape used as every sprites in group 1 bounding box.
     */
    protected final CollisionCircle circ1 = new CollisionCircle();

    /**
     * Default collision shape used as every sprites in group 2 bounding box.
     */
    protected final CollisionRect rect2 = new CollisionRect();

    /**
     * Indicates whether this collision detection should use pixel-perfect
     * precision or not.
     * <p>
     * The usual way to turn on this variable is :
     * 
     * <pre>
     * class ThisThatCollision extends BasicCollisionGroup
     * {
     * 
     *     // class initialization
     *     {
     *         pixelPerfectCollision = true;
     *     }
     * }
     * </pre>
     */
    public boolean pixelPerfectCollision;


    /**
     * *************************************************************************
     */
    /**
     * ***************************** CONSTRUCTOR *******************************
     */
    /**
     * *************************************************************************
     */

    /**
     * Creates new <code>BasicCollisionGroup</code>.
     */
    public CircleSpriteToSpriteCollisionGroup ()
    {}


    /**
     * Returns collision shape (bounding box) of specified sprite from group 1.
     * <p>
     * In this implementation, the sprite bounding box is set as large as
     * <code>Sprite</code> dimension:
     * 
     * <pre>
     * public CollisionShape getCollisionRect1 (Sprite s1)
     * {
     *     rect1.setBounds(s1.getX(), s1.getY(), s1.getWidth(), s1.getHeight());
     *     return rect1;
     * }
     * </pre>
     * 
     * @param s1 the sprite from group 1 to be check its collision
     * @return The collision shape of the sprite, or null to skip collision
     *         check.
     * @see #rect1
     * @see #getCollisionShape2(Sprite)
     * @see CollisionShape#intersects(CollisionShape)
     */
    public CollisionShape getCollisionShape1 (CircleSprite s1)
    {
        this.circ1.setBounds(s1.getX(), s1.getY(), s1.getRadius());

        return this.circ1;
    }


    /**
     * Returns collision shape (bounding box) of specified sprite from group 2.
     * <p>
     * In this implementation, the sprite bounding box is set as large as
     * <code>Sprite</code> dimension:
     * 
     * <pre>
     * public CollisionShape getCollisionRect2 (Sprite s2)
     * {
     *     rect2.setBounds(s2.getX(), s2.getY(), s2.getWidth(), s2.getHeight());
     *     return rect2;
     * }
     * </pre>
     * 
     * @param s2 the sprite from group 2 to be check its collision
     * @return The collision shape of the sprite, or null to skip collision
     *         check.
     * @see #rect2
     * @see #getCollisionShape1(Sprite)
     * @see CollisionRect#intersects(CollisionShape)
     */
    public CollisionShape getCollisionShape2 (Sprite s2)
    {
        this.rect2.setBounds(s2.getX(),
                             s2.getY(),
                             s2.getWidth(),
                             s2.getHeight());

        return this.rect2;
    }


    /**
     * *************************************************************************
     */
    /**
     * ****************** MAIN-METHOD: CHECKING COLLISION **********************
     */
    /**
     * *************************************************************************
     */

    @Override
    public void checkCollision ()
    {
        CircleSpriteGroup group1 = (CircleSpriteGroup) this.getGroup1();
        SpriteGroup group2 = this.getGroup2();
        if (!group1.isActive() || !group2.isActive())
        {
            // one of the group is not active
            return;
        }

        CircleSprite[] member1 = group1.getCircleSprites(); // group one members
        Sprite[] member2 = group2.getSprites();
        int size1 = group1.getSize(), // size of non-null members
        size2 = group2.getSize();

        CircleSprite sprite1;
        Sprite sprite2; // sprite reference
        CollisionShape shape1, shape2; // sprite collision shapes

        for (int i = 0; i < size1; i++)
        {
            sprite1 = member1[i];

            if (!sprite1.isActive() ||
                (shape1 = this.getCollisionShape1(sprite1)) == null)
            {
                // sprite do not want collision check
                continue;
            }

            for (int j = 0; j < size2; j++)
            {
                sprite2 = member2[j];

                if (!sprite2.isActive() || sprite1 == sprite2 ||
                    (shape2 = this.getCollisionShape2(sprite2)) == null)
                {
                    // sprite do not want collision check
                    continue;
                }

                if (this.isCollide(sprite1, sprite2, shape1, shape2))
                {
                    // fire collision event
                    this.collided(sprite1, sprite2);

                    // size1 = group1.getSize();
                    // size2 = group2.getSize();

                    if (!sprite1.isActive() ||
                        (shape1 = this.getCollisionShape1(sprite1)) == null)
                    {
                        // collided sprite has been dead
                        break;
                    }
                }
            }
        }
    }


    /**
     * Performs collision check between Sprite <code>s1</code> and Sprite
     * <code>s2</code>, and returns true if the sprites (<code>shape1</code>,
     * <code>shape2</code>) is collided.
     * <p>
     * Note: this method do not check active state of the sprites.
     * 
     * @param s1 sprite from group 1
     * @param s2 sprite from group 2
     * @param shape1 bounding box of sprite 1
     * @param shape2 bounding box of sprite 2
     * @return true, if the sprites is collided one another.
     */
    public boolean isCollide (CircleSprite s1,
                              Sprite s2,
                              CollisionShape shape1,
                              CollisionShape shape2)
    {
        if (!this.pixelPerfectCollision)
        {
            return (shape1.intersects(shape2));

        }
        else
        {
            if (shape1.intersects(shape2))
            {
                return isCirclePixelCollide(new Circle(s1.getX(),
                                                       s1.getY(),
                                                       s1.getRadius()),
                                            s2.getX(),
                                            s2.getY(),
                                            s2.getImage());
            }

            return false;
        }
    }


    public static boolean isCirclePixelCollide (double x,
                                                double y,
                                                double r,
                                                double x2,
                                                double y2,
                                                BufferedImage image2)
    {
        return isCirclePixelCollide(new Circle(x, y, r), x2, y2, image2);
    }


    /**
     * Estimates the area of the circle to the average of its inscribed and
     * inscribed in squares, then calculates exactly like pixel collision EXCEPT
     * only requires pixels of image to be non transparent
     * 
     * @param circle
     * @param x2
     * @param y2
     * @param image2
     * @return
     */
    public static boolean isCirclePixelCollide (Circle circle,
                                                double x2,
                                                double y2,
                                                BufferedImage image2)
    {
        Rectangle2D r1 = circle.inscribedSquare();
        Rectangle2D r2 = circle.inscribedInSquare();
        Rectangle2D r3 =
            new Rectangle2D.Double((r1.getX() + r2.getX()) / 2,
                                   (r1.getY() + r2.getY()) / 2,
                                   (r1.getWidth() + r2.getWidth()) / 2,
                                   (r1.getHeight() + r2.getHeight()) / 2);
        // initialization
        double width1 = r3.getX() + r3.getWidth() - 1, height1 =
            r3.getY() + r3.getHeight() - 1, width2 = x2 + image2.getWidth() - 1, height2 =
            y2 + image2.getHeight() - 1;

        int xstart = (int) Math.max(r3.getX(), x2), ystart =
            (int) Math.max(r3.getY(), y2), xend =
            (int) Math.min(width1, width2), yend =
            (int) Math.min(height1, height2);

        // intersection rect
        int toty = Math.abs(yend - ystart);
        int totx = Math.abs(xend - xstart);

        for (int y = 1; y < toty - 1; y++)
        {
            int ny = Math.abs(ystart - (int) r3.getY()) + y;
            int ny1 = Math.abs(ystart - (int) y2) + y;

            for (int x = 1; x < totx - 1; x++)
            {
                int nx = Math.abs(xstart - (int) r3.getX()) + x;
                int nx1 = Math.abs(xstart - (int) x2) + x;
                try
                {
                    if (((image2.getRGB(nx1, ny1) & 0xFF000000) != 0x00))
                    {
                        // collide!!
                        return true;
                    }
                }
                catch (Exception e)
                {
                    // System.out.println("s1 = "+nx+","+ny+" - s2 =
                    // "+nx1+","+ny1);
                }
            }
        }

        return false;

    }


    /**
     * Notified when <code>sprite1</code> from group 1 collided with
     * <code>sprite2</code> from group 2.
     * 
     * @param s1 sprite from group 1
     * @param s2 sprite from group 2
     */
    public abstract void collided (Sprite s1, Sprite s2);

}
